<?php
/**
 * ReportsRepo.php
 *
 * @copyright   2023 thingsworld.com.cn - All Rights Reserved
 * @link        http://thingsworld.com.cn/
 * @author      stiffer.chen <chenl@thingsworld.com.cn>
 * @created     2023-04-24 09:58:33
 * @modified    2023-04-24 09:58:33
 *
 */

namespace App\Repositories;

use App\Models\Customer;
use App\Models\Dept\Zone;
use App\Models\DeptModel;
use App\Models\Event;
use App\Models\Project;
use App\Models\Suggest;
use App\Models\User;
use App\Models\UserModel;
use App\Models\WorkOrder;
use App\Services\DashboardService;
use App\Services\DeptService;
use Carbon\Carbon;
use Carbon\CarbonPeriod;
use Illuminate\Support\Facades\DB;

class ReportsRepo
{
    const TYPE_WEEK = 'week';
    const TYPE_MONTH = 'month';
    const TYPE_QUARTER = 'quarter';
    const TYPE_YEAR = 'year';
    const TYPE_MONTH_SELECT = 'month_select';

    public static function zones()
    {
        $deptZones = Zone::query()
            ->from('dept_zone as dz')
            ->join('city as c', 'c.id', '=', 'dz.city_id')
            ->join('dept as d', 'd.id', '=', 'dz.dept_id')
            ->select(['dz.city_id', 'd.name', 'dz.dept_id'])
            ->get()
            ->groupBy('dept_id')
            ->toArray();

        $deptIds = array_keys($deptZones);

        $result = [];
        foreach ($deptIds as $deptId) {
            $currentDeptZone = $deptZones[$deptId];
            $zoneName = $currentDeptZone[0]['name'] ?? '';
            $cityIds = collect($currentDeptZone)->pluck('city_id')->toArray();
            $result[] = [
                'zone_id' => $deptId,
                'zone_name' => $zoneName,
                'city_ids' => $cityIds
            ];
        }
        return $result;
    }

    public static function types()
    {
        return [
            ['code' => self::TYPE_WEEK, 'title' => '周'],
            ['code' => self::TYPE_MONTH, 'title' => '月'],
            ['code' => self::TYPE_QUARTER, 'title' => '季度'],
            ['code' => self::TYPE_YEAR, 'title' => '年'],
            ['code' => self::TYPE_MONTH_SELECT, 'title' => '月份选择'],
        ];
    }

    /**
     * 平均响应用时
     *
     * month_select: 返回月份跨度: 2021-01, 2021-02, ...
     * week: 具体日期跨度: 2021-01-01, 2021-01-02, ...
     * month: 星期为跨度: 2023-01 第一周, 2023-01 第二周, ...
     * quarter: 月为跨度: 2021-01, 2021-02, ...
     * year: 月为跨度
     *
     * 从创建到指派的时间之和(小时) ➗期间工单总数量
     * @return array
     * @throws \Exception
     */
    public static function avgResponse()
    {
        $labels = self::getLabels();

        $timeRange = self::getSearchTimeRange();
        $start = $timeRange['start'];
        $end = $timeRange['end'];
        $stepTimeRange = self::getStepTimeRange();

        $woType = trim(request('wo_type', ''));

        $zoneIds = self::getZoneIds();

        // 工单
        $woBuilder = WorkOrder::query()
            ->where('create_time', '>=', Carbon::parse($start)->timestamp)
            ->where('create_time', '<=', Carbon::parse($end)->timestamp);
        if ($zoneIds) {
            $woBuilder->where(function ($query) use ($zoneIds) {
                $query->whereIn('province_id', $zoneIds)
                    ->orWhereIn('city_id', $zoneIds);
            });
        }
        if ($woType) {
            $woBuilder->where('type', '=', $woType);
        }
        $wos = $woBuilder
            ->select(['id', 'create_time'])
            ->get();
        // 工单历史
        $woHistories = WorkOrder\StatusHistory::query()
            ->from('work_order_status_history as h')
            ->join('work_order as wo', 'wo.id', '=', 'h.work_order_id')
            ->where('wo.create_time', '>=', Carbon::parse($start)->timestamp)
            ->where('wo.create_time', '<=', Carbon::parse($end)->timestamp)
            ->whereIn('h.status', [WorkOrderRepo::STATUS_CREATED, WorkOrderRepo::STATUS_ASSIGNED])
            ->select(['h.*'])
            ->get();
        $data = [];
        foreach ($stepTimeRange as $item) {
            // 时间范围内的工单数量
            $itemWos = $wos
                ->where('create_time', '>=', $item['start'])
                ->where('create_time', '<=', $item['end']);
            $itemWosCount = $itemWos->count();
            if ($itemWosCount <= 0) {
                $data[] = 0;
                continue;
            }
            $itemWoIds = $itemWos->pluck('id')->toArray();

            $itemTotal = 0;
            foreach ($woHistories->groupBy('work_order_id')->toArray() as $woId => $itemHistories) {
                if (!in_array($woId, $itemWoIds)) {
                    continue;
                }
                $itemStatusHistories = collect($itemHistories)->groupBy('status')->toArray();
                $itemStatusCreate = $itemStatusHistories[WorkOrderRepo::STATUS_CREATED][0] ?? [];
                $itemStatusAssign = $itemStatusHistories[WorkOrderRepo::STATUS_ASSIGNED][0] ?? [];
                if (empty($itemStatusCreate) || empty($itemStatusAssign)) {
                    continue;
                }
                $itemTotal += round(Carbon::parse($itemStatusCreate['create_time_format'])->diffInMinutes($itemStatusAssign['create_time_format']) / 60, 2);
            }
            if ($itemTotal == 0) {
                $data[] = 0;
            } else {
                $data[] = round($itemTotal / $itemWosCount, 2);
            }
        }
        return [
            'labels' => $labels,
            'data' => $data
        ];
    }

    private static function getZoneIds()
    {
        $zoneId = intval(request('zone_id', ''));
        if (!$zoneId) {
            return [];
        }
        $zones = collect(self::zones())->keyBy('zone_id')->toArray();
        $currentZone = $zones[$zoneId] ?? [];
        if (empty($currentZone)) {
            return [];
        }
        return $currentZone['city_ids'];
    }

    // 从创建到指派的时间之和(小时) ➗期间工单总数量
    public static function avgResponseDetail()
    {
        $timeRange = self::getSearchTimeRange();
        $start = $timeRange['start'];
        $end = $timeRange['end'];

        $limit = intval(request('limit', ''));
        if ($limit < 1) {
            $limit = 10;
        }

        // 工程师
        $users = User::query()
            ->select(['u.id as user_id', 'u.realname', 'wo.id as work_order_id'])
            ->from('user as u')
            ->join('work_order_user as wou', 'wou.user_id', '=', 'u.id')
            ->join('work_order as wo', 'wo.id', '=', 'wou.work_order_id')
            ->where('wou.is_master', 1)
            ->where('wo.create_time', '>=', Carbon::parse($start)->timestamp)
            ->where('wo.create_time', '<=', Carbon::parse($end)->timestamp)
            ->paginate($limit);
        $users->transform(function ($user) use ($users) {
            return self::avgResponseDetailWos($user, $users);
        });
        return $users;
    }

    private static function avgResponseDetailWos($user, $users)
    {
        $zoneIds = self::getZoneIds();
        $timeRange = self::getSearchTimeRange();
        $start = $timeRange['start'];
        $end = $timeRange['end'];
        $stepTimeRange = self::getStepTimeRange();
        $woType = trim(request('wo_type', ''));

        // 工单
        $woBuilder = WorkOrder::query()
            ->where('create_time', '>=', Carbon::parse($start)->timestamp)
            ->where('create_time', '<=', Carbon::parse($end)->timestamp);

        if ($zoneIds) {
            $woBuilder->where(function ($query) use ($zoneIds) {
                $query->whereIn('province_id', $zoneIds)
                    ->orWhereIn('city_id', $zoneIds);
            });
        }

        if ($woType) {
            $woBuilder->where('type', '=', $woType);
        }
        $wos = $woBuilder
            ->select(['id', 'create_time'])
            ->get();

        // 工单历史
        $woHistories = WorkOrder\StatusHistory::query()
            ->from('work_order_status_history as h')
            ->join('work_order as wo', 'wo.id', '=', 'h.work_order_id')
            ->where('wo.create_time', '>=', Carbon::parse($start)->timestamp)
            ->where('wo.create_time', '<=', Carbon::parse($end)->timestamp)
            ->whereIn('h.status', [WorkOrderRepo::STATUS_CREATED, WorkOrderRepo::STATUS_ASSIGNED])
            ->select(['h.*'])
            ->get()
            ->groupBy('work_order_id');

        $dataResults = [];

        foreach ($stepTimeRange as $itemTime) {
            $itemStart = $itemTime['start'];
            $itemEnd = $itemTime['end'];
            $itemWos = $wos
                ->where('create_time', '>=', $itemStart)
                ->where('create_time', '<=', $itemEnd);
            if (!$itemWos->count()) {
                $dataResults[] = [];
                continue;
            }
            $itemWoIds = $itemWos->pluck('id')->toArray();
            $itemDataResults = [];
            foreach ($woHistories as $hWoId => $itemHistories) {
                if (!in_array($hWoId, $itemWoIds)) {
                    continue;
                }
                $itemHistoryUsers = $users->keyBy('work_order_id')->toArray();
                $itemHistoryUser = $itemHistoryUsers[$hWoId] ?? [];
                if (!$itemHistoryUser) {
                    continue;
                }
                $itemUserId = $itemHistoryUser['user_id'];
                $itemHistoryArr = $itemHistories->groupBy('status')->toArray();
                $createHistory = $itemHistoryArr[WorkOrderRepo::STATUS_CREATED][0] ?? [];
                $assignHistory = $itemHistoryArr[WorkOrderRepo::STATUS_ASSIGNED][0] ?? [];
                if (empty($createHistory) || empty($assignHistory)) {
                    continue;
                }
                $itemDiffHour = round(Carbon::parse($assignHistory['create_time'])->diffInMinutes($createHistory['create_time']) / 60, 2);
                $itemDataResults[] = [
                    'user_id' => $itemUserId,
                    'hour' => $itemDiffHour,
                ];
            }
            $dataResults[] = $itemDataResults;
        }
        $userId = $user->user_id;
        $itemResult = [];
        foreach ($dataResults as $dataResult) {
            if (!$dataResult) {
                $itemResult[] = 0;
                continue;
            }
            $userDataResult = collect($dataResult)->groupBy('user_id')->toArray();
            if (empty($userDataResult)) {
                $itemResult[] = 0;
                continue;
            }
            $currentUserDataResult = $userDataResult[$userId] ?? [];
            if (empty($currentUserDataResult)) {
                $itemResult[] = 0;
                continue;
            }
            $itemDataCount = count($currentUserDataResult);
            $itemHourTotal = collect($currentUserDataResult)->sum('hour');
            $stepResult = round($itemHourTotal / $itemDataCount, 2);
            $itemResult[] = $stepResult;
        }
        return [
            'user_id' => $userId,
            'user_name' => $user->realname,
            'items' => $itemResult
        ];
    }

    /**
     * 重复维修率
     * 时间段内，重复维修的设备数量 / 总维修数量
     * @return array
     */
    public static function repeatRepair()
    {
        $labels = self::getLabels();

        $timeRange = self::getSearchTimeRange();
        $start = $timeRange['start'];
        $end = $timeRange['end'];

        $stepTimeRange = self::getStepTimeRange();
        $zoneIds = self::getZoneIds();
        $eventsBuilder = Event::query()
            ->where('create_time', '>=', $start)
            ->where('create_time', '<=', $end)
            ->select(['product_id', 'create_time'])
            ->where('product_id', '<>', 0)
            ->where(function ($query) {
                $query->where('type', '=', EventRepo::TYPE_REPAIR)
                    ->orWhere('type', '=', \App\Repositories\Customer\EventRepo::TYPE_REPAIR);
            });

        if ($zoneIds) {
            $eventsBuilder->where(function ($query) use ($zoneIds) {
                $query->whereIn('province_id', $zoneIds)
                    ->orWhereIn('city_id', $zoneIds);
            });
        }

        $events = $eventsBuilder->get();
        $data = [];

        foreach ($stepTimeRange as $item) {
            $itemEvents = $events
                ->where('create_time', '>=', $item['start'])
                ->where('create_time', '<=', $item['end'])
                ->groupBy('product_id')
                ->toArray();
            $itemMultiCount = 0;
            $itemTotalCount = 0;
            foreach ($itemEvents as $itemEvent) {
                if (count($itemEvent) > 1) {
                    $itemMultiCount++;
                }
                $itemTotalCount++;
            }
            if ($itemTotalCount <= 0) {
                $data[] = 0;
                continue;
            }
            $itemData = round($itemMultiCount / $itemTotalCount, 2);
            if (!$itemData) {
                $data[] = 0;
            } else {
                $data[] = $itemData;
            }
        }
        return [
            'labels' => $labels,
            'data' => $data
        ];
    }

    // 重复维修率明细
    // 重复维修的设备数量 / 总维修数量
    public static function repeatRepairDetail()
    {
        $timeRange = self::getSearchTimeRange();
        $start = $timeRange['start'];
        $end = $timeRange['end'];
        $stepTimeRange = self::getStepTimeRange();

        $types = [EventRepo::TYPE_REPAIR, \App\Repositories\Customer\EventRepo::TYPE_REPAIR];

        $limit = intval(request('limit', ''));
        if ($limit < 1) {
            $limit = 10;
        }

        $customers = Customer::query()
            ->from('customer as c')
            ->leftJoin('event as e', 'e.customer_id', '=', 'c.id')
            ->whereIn('e.type', $types)
            ->where('e.create_time', '>=', $start)
            ->where('e.create_time', '<=', $end)
            ->select(['c.id as customer_id', 'c.name as true_name', 'e.customer_name', 'e.id as event_id'])
            ->paginate($limit);

        $customers->transform(function ($customer) {
            return self::repeatRepairDetailForCustomer($customer);
        });
        return $customers;
    }

    private static function repeatRepairDetailForCustomer($customer)
    {
        $customerId = $customer->customer_id;
        $stepTimeRange = self::getStepTimeRange();
        $timeRange = self::getSearchTimeRange();
        $start = $timeRange['start'];
        $end = $timeRange['end'];
        $types = [EventRepo::TYPE_REPAIR, \App\Repositories\Customer\EventRepo::TYPE_REPAIR];
        $zoneIds = self::getZoneIds();
        $eventsBuilder = Event::query()
            ->from('event as e')
            ->where('e.create_time', '>=', $start)
            ->where('e.create_time', '<=', $end)
            ->whereIn('type', $types)
            ->select(['customer_id', 'product_id', 'e.create_time']);
        if ($zoneIds) {
            $eventsBuilder->where(function ($query) use ($zoneIds) {
                $query->whereIn('province_id', $zoneIds)
                    ->orWhereIn('city_id', $zoneIds);
            });
        }
        $events = $eventsBuilder->get()
            ->toArray();

        $customerItems = [];

        foreach ($stepTimeRange as $itemStep) {
            $itemStart = $itemStep['start'];
            $itemEnd = $itemStep['end'];
            $itemEvents = collect($events)
                ->where('create_time', '>=', $itemStart)
                ->where('create_time', '<=', $itemEnd);
            if (!$itemEvents->count()) {
                $customerItems[] = 0;
                continue;
            }
            $itemEventsArr = $itemEvents->groupBy('customer_id')->toArray();
            $currentItemEventArr = $itemEventsArr[$customerId] ?? [];
            if (!$currentItemEventArr) {
                $customerItems[] = 0;
                continue;
            }
            $count = count($currentItemEventArr);
            if (!$count) {
                $customerItems[] = 0;
                continue;
            }
            $multiProductIds = [];
            foreach ($currentItemEventArr as $itemEvent) {
                $multiProductIds[$itemEvent['product_id']] = ($multiProductIds[$itemEvent['product_id']] ?? 0) + 1;
            }
            $multiCount = collect($multiProductIds)->filter(function ($item) {
                return $item > 1;
            })->count();
            $customerItems[] = round($multiCount / $count, 2);
        }
        $result = [
            'customer_id' => $customerId,
            'customer_name' => $customer['customer_name'] ?: $customer['true_name'] ?: '',
            'data' => $customerItems
        ];
        return $result;
    }

    /**
     * 工单回执率
     * @return array
     */
    public static function woReceipt()
    {
        $labels = self::getLabels();
        $timeRange = self::getSearchTimeRange();
        $start = $timeRange['start'];
        $end = $timeRange['end'];
        $stepTimeRange = self::getStepTimeRange();

        $woType = trim(request('wo_type', ''));
        $zoneIds = self::getZoneIds();
        // 工单
        $woBuilder = WorkOrder::query()
            ->where('create_time', '>=', Carbon::parse($start)->timestamp)
            ->where('create_time', '<=', Carbon::parse($end)->timestamp);
        if ($zoneIds) {
            $woBuilder->where(function ($query) use ($zoneIds) {
                $query->whereIn('province_id', $zoneIds)
                    ->orWhereIn('city_id', $zoneIds);
            });
        }
        if ($woType) {
            $woBuilder->where('type', '=', $woType);
        }
        $wos = $woBuilder->get();
        $data = [];

        foreach ($stepTimeRange as $item) {
            // 时间范围内的工单数量
            $itemWos = $wos
                ->where('create_time', '>=', $item['start'])
                ->where('create_time', '<=', $item['end']);
            $itemWosCount = $itemWos->count();
            if (!$itemWosCount) {
                $data[] = 0;
                continue;
            }
            // 回访工单数量
            $receiptWos = WorkOrder\Receipt::query()
                ->select('work_order_id')
                ->where('create_time', '>=', Carbon::parse($item['start'])->timestamp)
                ->where('create_time', '<=', Carbon::parse($item['end'])->timestamp)
                ->distinct();
            $receiptCount = $receiptWos->count();
            if (!$receiptWos->count()) {
                $data[] = 0;
                continue;
            }
            $data[] = round($receiptCount / $itemWosCount, 2);
        }
        return [
            'labels' => $labels,
            'data' => $data
        ];
    }

    // 回执数量 / 工单总数量
    public static function woReceiptDetail()
    {
        $timeRange = self::getSearchTimeRange();
        $start = $timeRange['start'];
        $end = $timeRange['end'];

        $limit = intval(request('limit', ''));
        if ($limit < 1) {
            $limit = 10;
        }

        $usersBuilder = UserModel::query()
            ->from('user as u')
            ->join('work_order_user as wou', 'wou.user_id', '=', 'u.id')
            ->select(['u.id as user_id', 'u.realname'])
            ->where('wou.create_time', '>=', Carbon::parse($start)->timestamp)
            ->where('wou.create_time', '<=', Carbon::parse($end)->timestamp);
        $users = $usersBuilder->paginate($limit);
        $users->transform(function ($user) {
            return self::woReceiptDetailForUser($user);
        });
        return $users;
    }

    private static function woReceiptDetailForUser($user)
    {
        $timeRange = self::getSearchTimeRange();
        $start = $timeRange['start'];
        $end = $timeRange['end'];
        $woType = trim(request('wo_type', ''));
        $userId = $user->user_id;
        // 工单
        $woBuilder = WorkOrder::query()
            ->from('work_order as wo')
            ->join('work_order_user as wou', 'wou.work_order_id', '=', 'wo.id')
            ->select(['wo.id', 'wo.create_time', 'wou.user_id'])
            ->where('wo.create_time', '>=', Carbon::parse($start)->timestamp)
            ->where('wo.create_time', '<=', Carbon::parse($end)->timestamp);
        if ($woType) {
            $woBuilder->where('type', '=', $woType);
        }
        $wos = $woBuilder->get();

        $stepTimeRange = self::getStepTimeRange();
        $userWos = $wos->filter(function ($item) use ($userId) {
            return $item->user_id == $userId;
        });
        $userWoIds = array_unique($userWos->pluck('id')->toArray());
        $userWosCount = $userWos->count();
        $itemData = [];
        foreach ($stepTimeRange as $itemTimeRange) {
            $itemStart = Carbon::parse($itemTimeRange['start'])->timestamp;
            $itemEnd = Carbon::parse($itemTimeRange['end'])->timestamp;
            if (!$userWosCount) {
                $itemData[] = 0;
                continue;
            }
            $receiptCountData = WorkOrder\Receipt::query()
                ->whereIn('work_order_id', $userWoIds)
                ->where('create_time', '>=', Carbon::parse($itemStart)->timestamp)
                ->where('create_time', '<=', Carbon::parse($itemEnd)->timestamp)
                ->select(['work_order_id'])
                ->get()
                ->toArray();
            $receiptCount = count(array_unique(collect($receiptCountData)->pluck('work_order_id')->toArray()));
            if (!$receiptCount) {
                $itemData[] = 0;
                continue;
            }
            $itemData[] = round($receiptCount / $userWosCount, 2);
        }
        $result = [
            'user_id' => $userId,
            'user_name' => $user['realname'],
            'data' => $itemData
        ];
        return $result;
    }

    /**
     * 获取每个时间段的开始和结束时间
     *
     * @return array
     */
    private static function getStepTimeRange(): array
    {
        $labels = self::getLabels();
        $type = self::getType();
        $result = [];
        if ($type == self::TYPE_WEEK) {
            foreach ($labels as $label) {
                $result[] = [
                    'start' => Carbon::parse($label . ' 00:00:00')->toDateTimeString(),
                    'end' => Carbon::parse($label . ' 23:59:59')->toDateTimeString()
                ];
            }
        } elseif ($type == self::TYPE_MONTH) {
            $monthLabel = $labels[0];
            $currentMonth = explode(' ', $monthLabel)[0];
            $firstWeekStart = Carbon::parse($currentMonth . '-01')->toDateTimeString();
            $firstWeekEnd = Carbon::parse($firstWeekStart)->addDays(6)->toDateTimeString();
            $secondWeekStart = Carbon::parse($firstWeekEnd)->addDays()->toDateTimeString();
            $secondWeekEnd = Carbon::parse($secondWeekStart)->addDays(6)->toDateTimeString();

            $thirdWeekStart = Carbon::parse($secondWeekEnd)->addDays()->toDateTimeString();
            $thirdWeekEnd = Carbon::parse($thirdWeekStart)->addDays(6)->toDateTimeString();

            $forthWeekStart = Carbon::parse($thirdWeekEnd)->addDays()->toDateTimeString();
            $forthWeekEnd = Carbon::parse($forthWeekStart)->endOfWeek()->toDateTimeString();

            $result[] = ['start' => $firstWeekStart, 'end' => $firstWeekEnd];
            $result[] = ['start' => $secondWeekStart, 'end' => $secondWeekEnd];
            $result[] = ['start' => $thirdWeekStart, 'end' => $thirdWeekEnd];
            $result[] = ['start' => $forthWeekStart, 'end' => $forthWeekEnd];
        } elseif ($type == self::TYPE_QUARTER || $type == self::TYPE_YEAR || $type == self::TYPE_MONTH_SELECT) {
            foreach ($labels as $label) {
                $result[] = [
                    'start' => Carbon::parse($label)->startOfMonth()->toDateTimeString(),
                    'end' => Carbon::parse($label)->endOfMonth()->toDateTimeString()
                ];
            }
        }
        return $result;
    }

    /**
     * 获取搜索时间范围
     *
     * @return array
     * @throws \Exception
     */
    private static function getSearchTimeRange(): array
    {
        $labels = self::getLabels();
        $type = self::getType();
        $start = '';
        $end = '';
        if ($type == self::TYPE_MONTH_SELECT) {
            $start = Carbon::parse($labels[0])->startOfMonth();
            $end = Carbon::parse($labels[count($labels) - 1])->endOfMonth();
        } elseif ($type == self::TYPE_WEEK) {
            $start = $labels[0] . ' 00:00:00';
            $end = $labels[count($labels) - 1] . ' 23:59:59';
        } elseif ($type == self::TYPE_MONTH) {
            $monthLabel = $labels[0];
            $currentMonth = explode(' ', $monthLabel)[0];
            $start = Carbon::parse($currentMonth)->startOfMonth();
            $end = Carbon::parse($currentMonth)->endOfMonth();
        } elseif ($type == self::TYPE_QUARTER) {
            $startMonth = $labels[0];
            $endMonth = $labels[count($labels) - 1];
            $start = Carbon::parse($startMonth)->startOfMonth();
            $end = Carbon::parse($endMonth)->endOfMonth();
        } elseif ($type == self::TYPE_YEAR) {
            $startMonth = $labels[0];
            $endMonth = $labels[count($labels) - 1];
            $start = Carbon::parse($startMonth)->startOfMonth();
            $end = Carbon::parse($endMonth)->endOfMonth();
        }
        return [
            'start' => Carbon::parse($start)->timestamp,
            'end' => Carbon::parse($end)->timestamp
        ];
    }

    // 意见反馈
    public static function suggest()
    {
        $date = trim(request('date', ''));
        $projectId = intval(request('project_id', 0));
        if (!$projectId) {
            throw new \Exception('请选择所属项目');
        }
        if (!$date) {
            $date = Carbon::now()->toDateString();
        }
        try {
            $date = Carbon::parse($date)->toDateString();
        } catch (\Exception $e) {
            $date = Carbon::now()->toDateString();
        }
        $labels = self::getSuggestLabels($date);

        $start = $labels[0] . ' 00:00:00';
        $end = $labels[count($labels) - 1] . ' 23:59:59';

        $builder = Suggest::query()
            ->where('project_id', $projectId)
            ->where('create_time', '>=', Carbon::parse($start)->timestamp)
            ->where('create_time', '<=', Carbon::parse($end)->timestamp);
        $builder->with(['resolve.users']);

        $suggests = $builder->get();

        $one = $two = $three = $four = $five = $six = $seven = [
            'total_count' => 0,
            'resolve_count' => 0
        ];

        if (!$suggests->count()) {
            return [
                'labels' => $labels,
                'data' => [
                    $one, $two, $three, $four, $five, $six, $seven
                ]
            ];
        }

        foreach ($labels as $label) {
            $itemStart = $label . ' 00:00:00';
            $itemEnd = $label . ' 23:59:59';

            $totalCount = $resolveCount = 0;
            foreach ($suggests as $suggest) {
                $suggestCreateTime = Carbon::parse($suggest->create_time)->toDateTimeString();
                if ($suggestCreateTime >= $itemStart && $suggestCreateTime <= $itemEnd) {
                    $totalCount += 1;
                    if (!empty($suggest->resolve)) {
                        $resolveCount += 1;
                    }
                }
            }
            $itemTotal = [
                'total_count' => $totalCount,
                'resolve_count' => $resolveCount
            ];

            if ($label == $labels[0]) {
                $one = $itemTotal;
            }
            if ($label == $labels[1]) {
                $two = $itemTotal;
            }
            if ($label == $labels[2]) {
                $three = $itemTotal;
            }
            if ($label == $labels[3]) {
                $four = $itemTotal;
            }
            if ($label == $labels[4]) {
                $five = $itemTotal;
            }
            if ($label == $labels[5]) {
                $six = $itemTotal;
            }
            if ($label == $labels[6]) {
                $seven = $itemTotal;
            }
        }

        return [
            'labels' => $labels,
            'data' => [
                $one, $two, $three, $four, $five, $six, $seven
            ]
        ];
    }

    private static function getSuggestLabels($date)
    {
        $startDate = Carbon::parse($date)->startOfWeek()->toDateString();
        return [
            $startDate,
            Carbon::parse($startDate)->addDays(1)->toDateString(),
            Carbon::parse($startDate)->addDays(2)->toDateString(),
            Carbon::parse($startDate)->addDays(3)->toDateString(),
            Carbon::parse($startDate)->addDays(4)->toDateString(),
            Carbon::parse($startDate)->addDays(5)->toDateString(),
            Carbon::parse($startDate)->addDays(6)->toDateString(),
        ];
    }

    // 项目总览
    public static function taskReview()
    {
        $year = trim(request('year', ''));
        if ($year) {
            $year = Carbon::createFromFormat('Y', $year)->year;
            if (strlen($year) != 4) {
                throw new \Exception('年份格式错误');
            }
        } else {
            $year = Carbon::now()->year;
        }

        $start = Carbon::createFromFormat('Y', $year)->startOfYear()->timestamp;
        $end = Carbon::createFromFormat('Y', $year)->endOfYear()->timestamp;
        $allStatus = ProjectRepo::status();
        $scheduleStatus = ProjectRepo::scheduleStatus();

        $builder = Project::query()
            // ->select(['status'])
            ->where('create_time', '>=', $start)
            ->where('create_time', '<=', $end);
        $statusArr = $builder->get()->toArray();

        // 项目占比
        $statusProportion = [];
        // 进度占比
        $scheduleProportion = [];
        // 项目统计
        $statistics = [];

        foreach ($allStatus as $status) {
            $itemCount = 0;
            foreach ($statusArr as $detail) {
                if ($detail['status'] == $status['code']) {
                    $itemCount += 1;
                }
            }
            $statusProportion[] = [
                'status' => $status['title'],
                'count' => $itemCount
            ];
        }

        foreach ($scheduleStatus as $itemSchedule) {

            $unStartCount = 0; // 未开始
            $delayCount = 0; // 拖延
            $processingCount = 0; // 进行中
            $postponeCount = 0; // 延期
            $beforeCompleteCount = 0; // 提前完成
            $equalCompleteCount = 0; // 正常完成
            $afterCompleteCount = 0; // 拖延完成

            $itemScheduleCode = $itemSchedule['code'];
            $itemScheduleTitle = $itemSchedule['title'];

            $currentDay = Carbon::now()->toDateString();

            foreach ($statusArr as $detail) {
                $detailStatus = $detail['status'];
                $planStartAt = $detail['plan_start_at'];
                $planCompleteAt = $detail['plan_complete_at'];
                $completeAt = $detail['complete_at'];
                if ($detailStatus == ProjectRepo::STATUS_UN_START) {
                    if ($planStartAt <= $currentDay) {
                        $delayCount += 1;
                    } else {
                        $unStartCount += 1;
                    }
                }
                if ($detailStatus == ProjectRepo::STATUS_STARTED) {
                    if ($planCompleteAt <= $currentDay) {
                        $postponeCount += 1;
                    } else {
                        $processingCount += 1;
                    }
                }
                if ($detailStatus == ProjectRepo::STATUS_COMPLETED) {
                    if ($completeAt == $planCompleteAt) {
                        $equalCompleteCount += 1;
                    }
                    if ($completeAt < $planCompleteAt) {
                        $beforeCompleteCount += 1;
                    }
                    if ($completeAt > $planCompleteAt) {
                        $afterCompleteCount += 1;
                    }
                }
            }

            $tempCount = 0;
            // $scheduleProportion
            if ($itemScheduleCode == ProjectRepo::SCHEDULE_STATUS_UN_START) {
                $tempCount = $unStartCount;
            }
            if ($itemScheduleCode == ProjectRepo::SCHEDULE_STATUS_UN_DELAY) {
                $tempCount = $delayCount;
            }
            if ($itemScheduleCode == ProjectRepo::SCHEDULE_STATUS_PROCESSING) {
                $tempCount = $processingCount;
            }
            if ($itemScheduleCode == ProjectRepo::SCHEDULE_STATUS_POSTPONE) {
                $tempCount = $postponeCount;
            }
            if ($itemScheduleCode == ProjectRepo::SCHEDULE_STATUS_COMPLETE_BEFORE) {
                $tempCount = $beforeCompleteCount;
            }
            if ($itemScheduleCode == ProjectRepo::SCHEDULE_STATUS_COMPLETE_EQUAL) {
                $tempCount = $equalCompleteCount;
            }
            if ($itemScheduleCode == ProjectRepo::SCHEDULE_STATUS_COMPLETE_AFTER) {
                $tempCount = $afterCompleteCount;
            }

            $scheduleProportion[] = [
                'status' => $itemScheduleTitle,
                'count' => $tempCount
            ];
        }

        $months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
        $monthTitles = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
        foreach ($months as $index => $month) {
            if (strlen($month) < 2) {
                $month = '0' . $month;
            }
            $itemStart = Carbon::parse($year . "-{$month}")->startOfMonth()->toDateTimeString();
            $itemEnd = Carbon::parse($year . "-{$month}")->endOfMonth()->toDateTimeString();

            $completeCount = $unCompleteCount = 0;
            foreach ($statusArr as $item) {
                $itemStatus = $item['status'];
                $itemCreateTime = Carbon::parse($item['create_time'])->toDateTimeString();
                if ($itemStart <= $itemCreateTime && $itemCreateTime <= $itemEnd) {
                    if (in_array($itemStatus, [ProjectRepo::STATUS_COMPLETED, ProjectRepo::STATUS_FILING])) {
                        $completeCount += 1;
                    } else {
                        $unCompleteCount += 1;
                    }
                }
            }
            $statistics[] = [
                'title' => $year . "年" . $monthTitles[$index],
                'complete_count' => $completeCount,
                'un_complete_count' => $unCompleteCount
            ];
        }

        return [
            // 项目占比
            'status_proportion' => $statusProportion,
            // 进度占比
            'schedule_proportion' => $scheduleProportion,
            // 项目统计
            'statistics' => $statistics
        ];
    }

    // 服务人效
    public static function servicePerson($type, $monthStart, $monthEnd)
    {
        return (new DashboardService())->personEfficiency($type, $monthStart, $monthEnd);
    }

    // 服务平均用时
    public static function avgServiceTime($type, $monthStart, $monthEnd)
    {
        return (new DashboardService())->avgServiceTime($type, $monthStart, $monthEnd);
    }

    /**
     * 区域工单统计
     * 各状态工单数量 + 今日完成 + 超时工单
     * @param $deptId
     */
    public static function zoneWorkOrder($deptId)
    {
        if ($deptId) {
            $dept = DeptModel::find($deptId);
            if (empty($dept)) {
                throw new \Exception('无效的部门ID');
            }
        }
        $deptIds = self::getDeptIds($deptId);
        $wos = self::zoneWorkData($deptIds);
        $totals = self::zoneWorkOrderTotal($wos);
        $deptDatas = self::zoneWorkOrderDepts($deptId, $wos);
        return [
            'totals' => $totals,
            'dept_data' => $deptDatas
        ];
    }

    // 工单人员统计
    public static function personWorkOrder()
    {
        $limit = intval(request('limit', 10));
        $deptId = intval(request('dept_id', ''));
        $userId = intval(request('user_id', ''));

        $userBuilder = UserModel::query()->select(['id', 'realname']);
        if ($deptId) {
            $subDeptIds = (new DeptService())->getAllChildIds($deptId);
            $deptIds = array_unique(array_merge($subDeptIds, [$deptId]));
            $userBuilder->whereIn('dept_id', $deptIds);
        }
        if ($userId) {
            $userBuilder->where('id', $userId);
        }
        $userIds = $userBuilder->get()->pluck('id')->toArray();
        $counts = self::personWorkOrderItems($userIds);
        $users = $userBuilder->paginate($limit);
        $users->transform(function ($item) use ($counts) {

            $temp['user_id'] = $item['id'];
            $temp['user_name'] = $item['realname'];
            $currentCounts = $counts[$item['id']] ?? [];
            $temp['un_complete_count'] = $currentCounts['un_complete_count'] ?? 0;
            $temp['today_complete_count'] = $currentCounts['today_complete_count'] ?? 0;
            $temp['week_complete_count'] = $currentCounts['week_complete_count'] ?? 0;
            $temp['month_complete_count'] = $currentCounts['month_complete_count'] ?? 0;
            $temp['good_review_count'] = $currentCounts['good_review_count'] ?? 0;
            $temp['bad_review_count'] = $currentCounts['good_review_count'] ?? 0;
            return $temp;
        });
        return $users;
    }

    /**
     * 关单率: 完成工单数量、工单总数量
     */
    public static function closeWorkOrder()
    {
        $labels = self::getLabels();
        $data = [];
        $woItems = self::getWoItems();
        foreach ($woItems as $woItem) {
            $itemTotalCount = count($woItem);
            $item1 = $item2 = $item3 = $itemMore = 0;
            foreach ($woItem as $temp) {
                $histories = $temp->histories->keyBy('status')->toArray();
                $completeHistory = $histories[WorkOrderRepo::STATUS_COMPLETE] ?? [];
                if ($completeHistory) {
                    $completeDatetime = $completeHistory['create_time'];
                    $createHistory = $histories[WorkOrderRepo::STATUS_CREATED] ?? [];
                    $createDatetime = $createHistory['create_time'] ?? '';
                    if ($createHistory) {
                        $diffDay = Carbon::parse($createDatetime)->diffInDays($completeDatetime, true);
                        if ($diffDay <= 1) {
                            $item1 += 1;
                        } elseif ($diffDay <= 2) {
                            $item2 += 1;
                        } elseif ($diffDay <= 3) {
                            $item3 += 1;
                        } else {
                            $itemMore += 1;
                        }
                    }
                }

            }
            $item1Total = $item2Total = $item3Total = $itemMoreTotal = 0;
            if ($itemTotalCount > 0) {
                $item1Total = round($item1 / $itemTotalCount, 2);
                $item2Total = round($item2 / $itemTotalCount, 2);
                $item3Total = round($item3 / $itemTotalCount, 2);
                $itemMoreTotal = round($itemMore / $itemTotalCount, 2);
            }
            $data[] = [
                'item_1' => $item1Total,
                'item_2' => $item2Total,
                'item_3' => $item3Total,
                'item_more' => $itemMoreTotal
            ];
        }
        return [
            'labels' => $labels,
            'data' => $data
        ];
    }

    // 工单时效
    public static function workOrderAging()
    {
        $workOrderType = request('work_order_type', '');
        $deptId = request('dept_id', '');
        $deptIds = [];
        if ($deptId) {
            $deptIds[] = $deptId;
            $subDeptIds = (new DeptService())->getAllChildIds($deptId);
            $deptIds = array_unique(array_merge($deptIds, $subDeptIds));
        }
        $userId = request('user_id', 0);

        $labels = ['<=8小时', '8-24小时', '24-36小时', '36-48小时', '48-60小时', '60-72小时', '>72小时'];

        $wos = self::getWos();

        $data = [];
        foreach ($labels as $label) {
            $tempCount = 0;
            foreach ($wos as $wo) {
                $userIds = $wo->users->pluck('user_id')->toArray();
                if ($workOrderType && $wo->type != $workOrderType) {
                    continue;
                }
                $departmentId = $wo->customer->department_id ?? 0;
                if ($deptIds && !in_array($departmentId, $deptIds)) {
                    continue;
                }
                if ($userId && !in_array($userId, $userIds)) {
                    continue;
                }

                $histories = $wo->histories->keyBy('status')->toArray();
                $completeHistory = $histories[WorkOrderRepo::STATUS_COMPLETE] ?? [];
                $assignHistory = $histories[WorkOrderRepo::STATUS_ASSIGNED] ?? [];
                if (!$completeHistory || !$assignHistory) {
                    continue;
                }
                $diffHour = Carbon::parse($completeHistory['create_time'])->diffInHours(Carbon::parse($assignHistory['create_time']), true);
                if ($label == '<=8小时' && $diffHour <= 8) {
                    $tempCount += 1;
                }
                if ($label == '8-24小时' && ($diffHour > 8 && $diffHour <= 24)) {
                    $tempCount += 1;
                }
                if ($label == '24-36小时' && ($diffHour > 24 && $diffHour <= 36)) {
                    $tempCount += 1;
                }
                if ($label == '36-48小时' && ($diffHour > 36 && $diffHour <= 48)) {
                    $tempCount += 1;
                }
                if ($label == '48-60小时' && ($diffHour > 48 && $diffHour <= 60)) {
                    $tempCount += 1;
                }
                if ($label == '60-72小时' && ($diffHour > 60 && $diffHour <= 72)) {
                    $tempCount += 1;
                }
                if ($label == '>72小时' && $diffHour > 72) {
                    $tempCount += 1;
                }
            }
            $data[] = $tempCount;
        }

        return [
            'labels' => $labels,
            'data' => $data
        ];
    }

    // 工单趋势: 新增、完成
    public static function workOrderTendency()
    {
        $workOrderType = request('work_order_type', '');
        $deptId = request('dept_id', '');
        $deptIds = [];
        if ($deptId) {
            $deptIds[] = $deptId;
            $subDeptIds = (new DeptService())->getAllChildIds($deptId);
            $deptIds = array_unique(array_merge($deptIds, $subDeptIds));
        }

        $wos = self::getWoItems();

        $lables = self::getLabels();
        $data = [];
        foreach ($wos as $wo) {
            $newCount = 0;
            $completeCount = 0;
            foreach ($wo as $item) {
                if ($workOrderType && $item->type != $workOrderType) {
                    continue;
                }
                $departmentId = $item->customer->department_id ?? 0;
                if ($deptIds && !in_array($departmentId, $deptIds)) {
                    continue;
                }

                $status = $item->status;
                if ($status == WorkOrderRepo::STATUS_CREATED) {
                    $newCount += 1;
                }
                $histories = array_keys($item->histories->keyBy('status')->toArray());
                if (in_array(WorkOrderRepo::STATUS_COMPLETE, $histories)) {
                    $completeCount += 1;
                }
            }
            $data[] = [
                'new_count' => $newCount,
                'complete_count' => $completeCount
            ];
        }
        return [
            'labels' => $lables,
            'data' => $data
        ];
    }

    // 客户满意度
    public static function customerSatisfaction()
    {
        $labels = ['非常满意', '满意', '一般', '不满意', '非常不满意'];
        $workOrderType = request('work_order_type', '');
        $deptId = request('dept_id', '');
        $userId = request('user_id', '');
        $wos = self::getWos();
        $deptIds = [];
        if ($deptId) {
            $deptIds[] = $deptId;
            $subDeptIds = (new DeptService())->getAllChildIds($deptId);
            $deptIds = array_unique(array_merge($deptIds, $subDeptIds));
        }

        $data = [];

        foreach ($labels as $label) {
            $count = 0;
            foreach ($wos as $wo) {
                $userIds = $wo->users->pluck('user_id')->toArray();
                if ($workOrderType && $wo->type != $workOrderType) {
                    continue;
                }
                $departmentId = $wo->customer->department_id ?? 0;
                if ($deptIds && !in_array($departmentId, $deptIds)) {
                    continue;
                }
                if ($userId && !in_array($userId, $userIds)) {
                    continue;
                }
                $receipts = $wo->receipts->pluck('satisfaction')->toArray();
                foreach ($receipts as $receipt) {
                    if ($label == '非常满意' && $receipt == 5) {
                        $count += 1;
                    }
                    if ($label == '满意' && $receipt == 4) {
                        $count += 1;
                    }
                    if ($label == '一般' && $receipt == 3) {
                        $count += 1;
                    }
                    if ($label == '不满意' && $receipt == 2) {
                        $count += 1;
                    }
                    if ($label == '非常不满意' && $receipt <= 1) {
                        $count += 1;
                    }
                }
            }
            $data[] = $count;
        }

        // 好评率: 4分及以上 / 总计
        $saLables = self::getLabels();
        $saData = [];
        $saWos = self::getWoItems();
        foreach ($saWos as $itemWo) {
            $itemTotal = 0;
            $itemGood = 0;
            foreach ($itemWo as $tempWo) {
                $receipts = $tempWo->receipts;
                $itemTotal += $receipts->count();
                foreach ($receipts as $receipt) {
                    if ($receipt->satisfaction >= 4) {
                        $itemGood += 1;
                    }
                }
            }
            $itemSaData = 0;
            if ($itemTotal > 0 && $itemGood > 0) {
                $itemSaData = round($itemGood / $itemTotal, 2);
            }
            $saData[] = $itemSaData;
        }

        return [
            'labels' => $labels,
            'data' => $data,
            'sa_labels' => $saLables,
            'sa_data' => $saData
        ];
    }

    private static function getWoItems()
    {
        $wos = self::getWos();
        $type = self::getType();
        $labels = self::getLabels();
        $timeScope = self::getTimeScope();
        $start = $timeScope['start'];
        $end = $timeScope['end'];

        if ($type == self::TYPE_MONTH_SELECT) {
            $results = [];
            foreach ($labels as $label) {
                $itemStart = Carbon::parse($label)->startOfMonth()->toDateTimeString();
                $itemEnd = Carbon::parse($label)->endOfMonth()->toDateTimeString();
                $temp = [];
                foreach ($wos as $wo) {
                    $woCreateTime = Carbon::parse($wo->create_time)->toDateTimeString();
                    if ($woCreateTime >= $itemStart && $woCreateTime <= $itemEnd) {
                        $temp[] = $wo;
                    }
                }
                $results[] = $temp;
            }
            return $results;
        }

        if ($type == self::TYPE_WEEK) {
            $results = [];
            foreach ($labels as $label) {
                $itemStart = Carbon::parse($label)->toDateString() . ' 00:00:00';
                $itemEnd = Carbon::parse($label)->toDateString() . ' 23:59:59';
                $temp = [];
                foreach ($wos as $wo) {
                    $woCreateTime = Carbon::parse($wo->create_time)->toDateTimeString();
                    if ($woCreateTime >= $itemStart && $woCreateTime <= $itemEnd) {
                        $temp[] = $wo;
                    }
                }
                $results[] = $temp;
            }
            return $results;
        }

        if ($type == self::TYPE_MONTH) {
            $dateLabels = [];

            $oneStart = Carbon::parse($start)->toDateTimeString();
            $oneEnd = Carbon::parse($oneStart)->addDays(6)->toDateString() . ' 23:59:59';
            $dateLabels[] = [
                'start' => $oneStart,
                'end' => $oneEnd,
            ];
            $twoStart = Carbon::parse($oneEnd)->addDays(1)->toDateString() . ' 00:00:00';
            $twoEnd = Carbon::parse($twoStart)->addDays(6)->toDateString() . ' 23:59:59';
            $dateLabels[] = [
                'start' => $twoStart,
                'end' => $twoEnd,
            ];
            $threeStart = Carbon::parse($twoEnd)->addDays(1)->toDateString() . ' 00:00:00';
            $threeEnd = Carbon::parse($threeStart)->addDays(6)->toDateString() . ' 23:59:59';
            $dateLabels[] = [
                'start' => $threeStart,
                'end' => $threeEnd,
            ];
            $fourStart = Carbon::parse($threeEnd)->addDays(1)->toDateString() . ' 00:00:00';
            $fourEnd = Carbon::parse($fourStart)->endOfMonth()->toDateString() . ' 23:59:59';
            $dateLabels[] = [
                'start' => $fourStart,
                'end' => $fourEnd,
            ];
            $results = [];
            foreach ($dateLabels as $dateLabel) {
                $tempStart = $dateLabel['start'];
                $tempEnd = $dateLabel['end'];
                $temp = [];
                foreach ($wos as $wo) {
                    $woCreateTime = Carbon::parse($wo->create_time)->toDateTimeString();
                    if ($woCreateTime >= $tempStart && $woCreateTime <= $tempEnd) {
                        $temp[] = $wo;
                    }
                }
                $results[] = $temp;
            }
            return $results;
        }

        if ($type == self::TYPE_QUARTER) {
            $results = [];
            foreach ($labels as $label) {
                $start = Carbon::parse($label)->startOfMonth()->toDateTimeString();
                $end = Carbon::parse($label)->endOfMonth()->toDateTimeString();
                $temp = [];
                foreach ($wos as $wo) {
                    $woCreateTime = Carbon::parse($wo->create_time)->toDateTimeString();
                    if ($woCreateTime >= $start && $woCreateTime <= $end) {
                        $temp[] = $wo;
                    }
                }
                $results[] = $temp;
            }
            return $results;
        }

        if ($type == self::TYPE_YEAR) {
            $results = [];
            foreach ($labels as $label) {
                $start = Carbon::parse($label)->startOfMonth()->toDateTimeString();
                $end = Carbon::parse($label)->endOfMonth()->toDateTimeString();
                $temp = [];
                foreach ($wos as $wo) {
                    $woCreateTime = Carbon::parse($wo->create_time)->toDateTimeString();
                    if ($woCreateTime >= $start && $woCreateTime <= $end) {
                        $temp[] = $wo;
                    }
                }
                $results[] = $temp;
            }
            return $results;
        }
        return [];
    }

    private static function getWos()
    {
        $timeScope = self::getTimeScope();
        $start = $timeScope['start'];
        $end = $timeScope['end'];
        $type = trim(request('work_order_type', ''));
        $builder = WorkOrder::query()
            ->where('create_time', '>=', strtotime($start))
            ->where('create_time', '<=', strtotime($end));
        if ($type) {
            $builder->where('type', '=', $type);
        }
        $builder->with(['histories', 'customer', 'receipts', 'users']);
        return $builder->get();
    }

    /**
     * 获取x轴标签
     * month_select: 返回月份跨度: 2021-01, 2021-02, ...
     * week: 具体日期跨度: 2021-01-01, 2021-01-02, ...
     * month: 星期为跨度: 2023-01 第一周, 2023-01 第二周, ...
     * quarter: 月为跨度: 2021-01, 2021-02, ...
     * year: 月为跨度
     *
     * @throws \Exception
     */
    private static function getLabels()
    {
        $timeScope = self::getTimeScope();
        $start = $timeScope['start'];
        $end = $timeScope['end'];
        $type = self::getType();

        if ($type == self::TYPE_MONTH_SELECT) {
            $startYear = Carbon::parse($start)->year;
            $startMonth = Carbon::parse($start)->month;
            $startDate = Carbon::parse("{$startYear}-{$startMonth}")->startOfMonth()->toDateString();

            $endYear = Carbon::parse($end)->year;
            $endMonth = Carbon::parse($end)->month;
            $endDate = Carbon::parse("{$endYear}-{$endMonth}")->endOfMonth()->toDateString();

            $diffMonth = Carbon::parse($startDate)->diffInMonths($endDate, true);

            $labels = [];
            for ($i = 0; $i <= $diffMonth; $i++) {
                $itemDate = Carbon::parse($startDate)->addMonths($i);
                $itemYear = $itemDate->year;
                $itemMonth = $itemDate->month;
                if ($itemMonth < 10) {
                    $itemMonth = "0" . $itemMonth;
                }
                $labels[] = "{$itemYear}-{$itemMonth}";
            }
            return $labels;
        }

        if ($type == self::TYPE_WEEK) {
            $labels = [];
            $labels[] = Carbon::parse($start)->toDateString();
            $labels[] = Carbon::parse($start)->addDays(1)->toDateString();
            $labels[] = Carbon::parse($start)->addDays(2)->toDateString();
            $labels[] = Carbon::parse($start)->addDays(3)->toDateString();
            $labels[] = Carbon::parse($start)->addDays(4)->toDateString();
            $labels[] = Carbon::parse($start)->addDays(5)->toDateString();
            $labels[] = Carbon::parse($start)->addDays(6)->toDateString();
            return $labels;
        }

        if ($type == self::TYPE_MONTH) {
            $year = Carbon::parse($start)->year;
            $month = Carbon::parse($start)->month;
            if ($month < 10) {
                $month = "0" . $month;
            }
            $labelPrefix = "{$year}-{$month}";
            $labels = [];
            $labels[] = $labelPrefix . " 第一周";
            $labels[] = $labelPrefix . " 第二周";
            $labels[] = $labelPrefix . " 第三周";
            $labels[] = $labelPrefix . " 第四周";
            return $labels;
        }

        if ($type == self::TYPE_QUARTER) {
            $startYear = Carbon::parse($start)->year;
            $startMonth = Carbon::parse($start)->month;
            if ($startMonth < 10) {
                $startMonth = "0" . $startMonth;
            }
            $itemStart = "{$startYear}-{$startMonth}";
            $labels = [];
            $labels[] = $itemStart;

            $itemTwoYear = Carbon::parse($itemStart)->addMonths(1)->year;
            $itemTwoMonth = Carbon::parse($itemStart)->addMonths(1)->month;
            if ($itemTwoMonth < 10) {
                $itemTwoMonth = "0" . $itemTwoMonth;
            }
            $labels[] = "{$itemTwoYear}-{$itemTwoMonth}";

            $itemThreeYear = Carbon::parse($itemStart)->addMonths(2)->year;
            $itemThreeMonth = Carbon::parse($itemStart)->addMonths(2)->month;
            if ($itemThreeMonth < 10) {
                $itemThreeMonth = "0" . $itemThreeMonth;
            }
            $labels[] = "{$itemThreeYear}-{$itemThreeMonth}";
            return $labels;
        }

        if ($type == self::TYPE_YEAR) {
            $labels = [];

            $startYear = Carbon::parse($start)->year;
            $startMonth = Carbon::parse($start)->month;
            if ($startMonth < 10) {
                $startMonth = "0" . $startMonth;
            }

            $itemStart = "{$startYear}-{$startMonth}";
            $labels[] = $itemStart;
            for ($i = 1; $i < 12; $i++) {
                $tempDate = Carbon::parse($itemStart)->addMonths($i);
                $tempYear = $tempDate->year;
                $tempMonth = $tempDate->month;
                if ($tempMonth < 10) {
                    $tempMonth = "0" . $tempMonth;
                }
                $labels[] = "{$tempYear}-{$tempMonth}";
            }
            return $labels;
        }

        return [];
    }

    private static function getType()
    {
        $type = request('type', '');
        $types = collect(self::types())->pluck('code')->toArray();
        if (!in_array($type, $types)) {
            $type = self::TYPE_MONTH_SELECT;
        }
        return $type;
    }

    // 获取时间范围
    private static function getTimeScope()
    {
        // 时间
        $type = self::getType();
        $startTime = ''; // 开始时间
        $endTime = ''; // 结束时间
        if ($type == self::TYPE_MONTH_SELECT) {
            $monthStart = request('month_start', '');
            $monthEnd = request('month_end', '');
            if (!$monthStart) {
                $monthStart = Carbon::now()->addMonths(-1)->startOfMonth()->toDateString();
            }
            if (!$monthEnd) {
                $monthEnd = Carbon::now()->endOfMonth()->toDateString();
            }
            if (!$monthStart || !$monthEnd) {
                throw new \Exception('开始月份和结束月份不能为空');
            }
            try {
                $startTime = Carbon::parse($monthStart)->startOfMonth()->toDateTimeString();
            } catch (\Exception $e) {
                throw new \Exception('开始月份错误');
            }
            try {
                $endTime = Carbon::parse($monthEnd)->endOfMonth()->toDateTimeString();
            } catch (\Exception $e) {
                throw new \Exception('结束月份错误');
            }
        }

        $now = Carbon::now();
        if ($type == self::TYPE_WEEK) {
            $startTime = $now->startOfWeek()->toDateTimeString();
            $endTime = $now->endOfWeek()->toDateTimeString();
        }
        if ($type == self::TYPE_MONTH) {
            $startTime = $now->startOfMonth()->toDateTimeString();
            $endTime = $now->endOfMonth()->toDateTimeString();
        }

        $year = $now->year;

        if ($type == self::TYPE_QUARTER) {
            $currentMonth = $now->month;
            $currentQuarter = intval(ceil($currentMonth / 3)); // 当前季度
            switch ($currentQuarter) {
                case 1:
                    $startTime = Carbon::parse($year . "-01")->startOfMonth()->toDateTimeString();
                    $endTime = Carbon::parse($year . "-03")->endOfMonth()->toDateTimeString();
                    break;
                case 2:
                    $startTime = Carbon::parse($year . "-04")->startOfMonth()->toDateTimeString();
                    $endTime = Carbon::parse($year . "-06")->endOfMonth()->toDateTimeString();
                    break;
                case 3:
                    $startTime = Carbon::parse($year . "-07")->startOfMonth()->toDateTimeString();
                    $endTime = Carbon::parse($year . "-09")->endOfMonth()->toDateTimeString();
                    break;
                case 4:
                    $startTime = Carbon::parse($year . "-10")->startOfMonth()->toDateTimeString();
                    $endTime = Carbon::parse($year . "-12")->endOfMonth()->toDateTimeString();
                    break;
            }
        }
        if ($type == self::TYPE_YEAR) {
            $startTime = $now->startOfYear()->toDateTimeString();
            $endTime = $now->endOfYear()->toDateTimeString();
        }

        return [
            'start' => $startTime,
            'end' => $endTime
        ];
    }

    public static function personWorkOrderItems($userIds)
    {
        $workOrdersBuilder = WorkOrder::query()
            ->select(['wo.*'])
            ->from('work_order as wo')
            ->join('work_order_user as wou', 'wo.id', '=', 'wou.work_order_id')
            ->whereIn('wou.user_id', $userIds);
        $workOrdersBuilder->with(['histories', 'users', 'receipts']);

        $wos = $workOrdersBuilder->get();

        $results = [];
        foreach ($userIds as $userId) {
            $unCompleteCount = 0; // 未完成
            $todayCompleteCount = 0; // 今日完成
            $weekCompleteCount = 0; // 本周完成
            $monthCompleteCount = 0; // 本月完成
            $goodReviewCount = 0; // 好评数
            $badReviewCount = 0; // 好评数

            $todayStart = Carbon::now()->toDateString() . ' 00:00:00';
            $todayEnd = Carbon::now()->toDateString() . ' 23:59:59';

            $weekStart = Carbon::now()->startOfWeek()->toDateTimeString();
            $weekEnd = Carbon::now()->endOfWeek()->toDateTimeString();

            $monthStart = Carbon::now()->startOfMonth()->toDateTimeString();
            $monthEnd = Carbon::now()->endOfMonth()->toDateTimeString();

            foreach ($wos as $wo) {
                $itemUserIds = array_keys($wo->users->keyBy('user_id')->toArray());
                if (!in_array($userId, $itemUserIds)) {
                    continue;
                }
                $histories = $wo->histories->keyBy('status');
                $itemHistories = array_keys($histories->toArray());
                if (!in_array(WorkOrderRepo::STATUS_COMPLETE, $itemHistories)) {
                    $unCompleteCount += 1;
                }
                $allHistories = $wo->histories->keyBy('status');
                $completeHistory = $allHistories[WorkOrderRepo::STATUS_COMPLETE] ?? null;
                if (!empty($completeHistory)) {
                    $completeCreateTime = Carbon::parse($completeHistory->create_time)->toDateTimeString();

                    if ($completeCreateTime >= $todayStart && $completeCreateTime <= $todayEnd) {
                        $todayCompleteCount += 1;
                    }
                    if ($completeCreateTime >= $weekStart && $completeCreateTime <= $weekEnd) {
                        $weekCompleteCount += 1;
                    }
                    if ($completeCreateTime >= $monthStart && $completeCreateTime <= $monthEnd) {
                        $monthCompleteCount += 1;
                    }
                }
                $receipts = $wo->receipts;
                if ($receipts->count()) {
                    foreach ($receipts as $receipt) {
                        // 评价 > 3 视为好评
                        if ($receipt->satisfaction >= 3) {
                            $goodReviewCount += 1;
                        } else {
                            $badReviewCount += 1;
                        }
                    }
                }
            }

            $results[$userId] = [
                'un_complete_count' => $unCompleteCount,
                'today_complete_count' => $todayCompleteCount,
                'week_complete_count' => $weekCompleteCount,
                'month_complete_count' => $weekCompleteCount,
                'good_review_count' => $goodReviewCount,
                'bad_review_count' => $badReviewCount,
            ];
        }
        return $results;
    }

    private static function zoneWorkOrderDepts($deptId, $wos)
    {
        if ($deptId) {
            $dept = DeptModel::find($deptId);
            $deptNames = [
                ['id' => $deptId, 'name' => $dept->name]
            ];
        } else {
            $topDept = DeptModel::query()
                ->where('name', '售后部')
                ->first();
            $topPid = $topDept->id ?? 0;
            $deptNames = DeptModel::query()
                ->select(['name', 'id'])
                ->where('pid', $topPid)
                ->get()
                ->toArray();
        }
        $results = [];
        foreach ($deptNames as $item) {
            $itemDeptId = $item['id'];
            $allDeptIds = self::getDeptIds($itemDeptId);
            $deptProvinceIds = self::deptZones($allDeptIds);
            $unCompleteCount = $completeCount = 0;
            foreach ($wos as $wo) {
                $provinceId = $wo->province_id;
                if (!in_array($provinceId, $deptProvinceIds)) {
                    continue;
                }
                $histories = array_keys($wo->histories->keyBy('status')->toArray());
                if (in_array(WorkOrderRepo::STATUS_COMPLETE, $histories)) {
                    $completeCount += 1;
                } else {
                    $unCompleteCount += 1;
                }
            }
            $results[] = [
                'name' => $item['name'],
                'complete_count' => $completeCount,
                'un_complete_count' => $unCompleteCount
            ];
        }
        return $results;
    }

    private static function deptZones($deptIds)
    {
        return Zone::query()
            ->select(['city_id'])
            ->whereIn('dept_id', $deptIds)
            ->pluck('city_id')
            ->toArray();
    }

    private static function zoneWorkOrderTotal($wos)
    {
        // 待指派
        $createdCount = 0;
        // 已指派
        $assignedCount = 0;
        // 已接受
        $acceptCount = 0;
        // 进行中
        $beginCount = 0;
        // 今日完成
        $todayCount = 0;
        // 超时
        $timeoutCount = 0;

        foreach ($wos as $wo) {
            $status = $wo->status;
            if ($status == WorkOrderRepo::STATUS_CREATED) {
                $createdCount += 1;
            }
            if ($status == WorkOrderRepo::STATUS_ASSIGNED) {
                $assignedCount += 1;
            }
            if ($status == WorkOrderRepo::STATUS_ACCEPT) {
                $acceptCount += 1;
            }
            if ($status == WorkOrderRepo::STATUS_BEGIN) {
                $beginCount += 1;
            }
            $completeHistoryTime = $wo->histories->keyBy('status')->toArray()[WorkOrderRepo::STATUS_COMPLETE]['create_time_format'] ?? '';
            if ($status == WorkOrderRepo::STATUS_COMPLETE) {
                if ($completeHistoryTime && Carbon::parse($completeHistoryTime)->toDateString() == Carbon::now()->toDateString()) {
                    $todayCount += 1;
                }
            }
            /**
             * 超时
             * 未完成: 当前时间 > 计划时间
             * 已完成: 完成时间 > 计划时间
             */
            $planAt = Carbon::parse($wo->plan_at)->toDateString();
            $now = Carbon::now()->toDateString();
            if (!$completeHistoryTime && $now > $planAt) {
                $timeoutCount += 1;
            }
            if ($completeHistoryTime && Carbon::parse($completeHistoryTime)->toDateString() > $planAt) {
                $timeoutCount += 1;
            }
        }

        return [
            'created' => $createdCount,
            'assigned' => $assignedCount,
            'accept' => $acceptCount,
            'begin' => $beginCount,
            'today' => $todayCount,
            'timeout' => $timeoutCount
        ];
    }

    private static function zoneWorkData($deptIds)
    {
        $builder = WorkOrder::query();
        $builder->with(['histories', 'customer']);
        $builder->select(['work_order.*']);
        if ($deptIds) {
            $builder->join('customer', 'customer.id', '=', 'work_order.customer_id')
                ->whereIn('customer.department_id', $deptIds);
        }
        return $builder->get();
    }

    private static function getDeptIds($deptId)
    {
        if (!$deptId) {
            return [];
        }
        $deptIds = (new DeptService())->getAllChildIds($deptId);
        $deptIds[] = $deptId;
        return array_unique($deptIds);
    }
}